/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component.situations;

import static java.lang.Math.abs;

import java.util.Arrays;
import java.util.LinkedList;

import robocup.component.ControllerNumbers;
import robocup.component.DAInamiteConstants;
import robocup.component.SConf;
import robocup.component.WorldModel;
import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.ControllerNumbers.STAMINA_LEVEL;
import robocup.component.ControllerNumbers.TEAM;
import robocup.component.actionfactories.BasicActionFactory;
import robocup.component.actions.Action;
import robocup.component.actions.DashAction;
import robocup.component.actions.TurnAction;
import robocup.component.geometry.Circle;
import robocup.component.geometry.Vektor;
import robocup.component.speechacts.MessageFactory.MESSAGES;
import robocup.component.worldmodel.PlayersModel;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Line;
import robocup.component.worldobjects.Player;
import robocup.component.worldobjects.Player.RELATIVE;

/**
 * This situation finds out which player will intercept the ball 
 * first and what interception times will have the next players. It also
 * calculates the resulting intercept action for the fastest player of each 
 * team. If we are the fastest player to intercept the ball it calculates
 * where the ball should be intercepted by using the {@link SituationEvaluator}.
 * <br>
 * Ball interception is one of the most important things to do and will nearly 
 * always be triggered when we are the fastest player to the ball. It is one of 
 * the most important team skills.
 */
public class InterceptBallSituation extends Situation {

  //if a player does not intercept the ball after this number of cycles
  //he is not considered
	private int maxCycles = DAInamiteConstants.PROPHET_MAX_CYCLES_INTERCEPTION;

  //if an opponent intercepts we will still look for teammates that intercept
  //in the following number of cycles
	private int maxTimeDiffToOpponent = 5;

  //if a teammate intercepts we will still look for opponents that intercept
  //in the following number of cycles.
	private int maxTimeDiffToTeammate = 5;

  //number of max. cycles when intercepting backwards (instead of turning and in
  //tercepting forwards)
	private int maxBackwards = DAInamiteConstants.PROPHET_MAX_BACKWARD;

  //the tolerance due to noise for teammates
  //-> kickable margin of teammates is reduced by this
	double securityMates = 0.2;

  //the tolerance due to noise for opponents
  //-> kickable margin of opponents is reduced by this
	double securityOthers = 0.0;

	// used to avoid heavy Vektor creation, take care on side effects!
	private Vektor dummyVektor = new Vektor();

  //used for ball position calculations
	private Vektor correctedBallPos = new Vektor();

  /**
   * reference to the {@link PlayersModel}{@link #expectedPlayers}
   */
	private Player[] expectedPlayers;

  /**
   * the interception time for each player
   */
	private int[] interceptionTimes = new int[22];

  /**
   * the interception times for this player.
   * How much time do we need to get to the point where the ball will be in
   * <code>myInterceptionTimes</code> cycles. Negative if we don't reach that 
   * point in time.
   */
	private int[] myInterceptionTimes = new int[this.maxCycles];

  /**
   * The interception time to choose from <code>{@link #myInterceptionTimes}
   * </code>.
   */
	private int myBestInterceptionTime = Integer.MAX_VALUE;

  /**
   * the time left before an opponent will get the ball when when intercepting
   * after <code>{@link #myBestInterceptionTime}</code> cycles
   */
	private int myCatchSecurity = Integer.MAX_VALUE;

  /**
   * the interception mode when interception in a given number of cycles.
   * 
   * @see Movements#playerMovements
   */
	private int[] myInterceptionType = new int[this.maxCycles];

  /**
   * number of cycles till the ball will be outside
   */
	private int ballOutsideTime = Integer.MAX_VALUE;

  /**
   * the ball will go into our goal.
   */
	private boolean ballWillGoIntoOwnGoal = false;

  /**
   * the ball will go into the opponent's goal.
   */
	private boolean ballWillGoIntoOpponentsGoal = false;

  /**
   * who will get the ball first?
   */
	private TEAM inBallPossession = TEAM.UNCERTAIN;

  /**
   * who kicked last? (i.e. important after standard situations)
   */
	private Player lastKickingPlayer = null;

  /**
   * the drift for each player caused by its inertia.
   */
	private Vektor[] drift = new Vektor[22];

  /**
   * each players speed.
   */
	private Vektor[] playerSpeed = new Vektor[22];

  /**
   * the ball position for each cycle.
   */
	private Vektor[] ballPositions = new Vektor[this.maxCycles];

  /**
   * the next action for each player.
   * 
   * we calculate opponents actions as well as we use them for our prediction 
   * model. This is very important as if we would not predict the interception
   * actions of the fastest players the <code>{@link #inPossessionOfBall()}
   * </code> flag might change very often leading to inconsistency.
   */
	private Action[] nextAction = new Action[22];

  /**
   * if flase don't care about this player as he will not be able to intercept.
   */
	private boolean[] considerPlayer = new boolean[22];

  /**
   * the actions to be done by this player when intercepting in a given cycle.
   * null if no interception is possible for this cycle.
   */
	private Action[] myInterceptActions = new Action[this.maxCycles];

  /**
   * this distance is needed by a player to control the ball. this distance 
   * differs from player to player due to different kick margins. it may also
   * differ due to the factors <code>securityMates|securityOthers</code> or for 
   * the goalies as they can catch the ball.
   */
	private double[] secureDistance = new double[22];

	// init dummy player so we don't have to clone later
	Player p = Player.getDummyInstance();

	Player p2 = Player.getDummyInstance();

	Ball b = Ball.getDummyInstance();

	/**
   * reference to {@link Movements}{@link #playerMovements}
	 */
	public double[][][] playerMovements;

  /**
   * the resulting ip.
   */
	private Vektor ip = new Vektor();

  /**
   * the resulting interception time.
   */
	private int interceptionTime = 50;

  /**
   * the players that are fastest to the ball. tolerance is defined by
   * <code>maxTimeDiffToOpponent|maxTimeDiffToTeammate</code>
   */
	private LinkedList<Player> fastestPlayers = new LinkedList<Player>();

  /**
   * all players that can kick the ball in the current cycle.
   */
	private LinkedList<Player> playersThatCanKick = new LinkedList<Player>();

  /**
   * true if this player can intercept in a previous cycle.
   */
	private boolean meAlreadyIntercepted = false;

	private boolean DEBUG = false;

  /**
   * true if a teammate can intercept in a previous cycle.
   */
	private boolean friendAlreadyIntercepted;

  /**
   * this players intercept action was already calculated. don't calculate it 
   * again.
   */
	private boolean myNextActionWasCalculated;

	/**
	 * A situation which calculates, which player is fastest to the ball,
	 * assuming they are willing to reach it as fast as possible
	 * 
	 * @param world -
	 *            the current world-model
	 * @param movements -
	 *            a generic movement model
	 */
	public InterceptBallSituation(WorldModel world, Movements movements) {
		super(world, movements);

    //set references and fill arrays
		this.expectedPlayers = world.getExpectedPlayers();
		this.playerMovements = movements.playerMovements;
		Arrays.fill(this.interceptionTimes, Integer.MAX_VALUE);
		Arrays.fill(this.nextAction, null);
		for (int i = 0; i < this.drift.length; i++) {
			this.drift[i] = new Vektor();
		}
		for (int i = 0; i < this.playerSpeed.length; i++) {
			this.playerSpeed[i] = new Vektor();
		}
		// create ball positions so we don't have to clone them later
		for (int i = 0; i < this.ballPositions.length; i++) {
			this.ballPositions[i] = new Vektor();
		}
	}

	/**
	 * resets all local variables.
	 * 
	 */
	private void reset() {
		this.maxCycles = DAInamiteConstants.PROPHET_MAX_CYCLES_INTERCEPTION;
		this.interceptionTime = Integer.MAX_VALUE;
		this.myBestInterceptionTime = Integer.MAX_VALUE;

		this.fastestPlayers.clear();
		this.playersThatCanKick.clear();

		this.ballOutsideTime = Integer.MAX_VALUE;
		this.meAlreadyIntercepted = false;
		this.friendAlreadyIntercepted = false;
		this.benefit = 0.0;
		Arrays.fill(this.interceptionTimes, Integer.MAX_VALUE);
		Arrays.fill(this.myInterceptionTimes, Integer.MAX_VALUE);
		Arrays.fill(this.nextAction, null);
		Arrays.fill(this.myInterceptActions, null);
		Arrays.fill(this.myInterceptionType, -1);

		for (int i = 0; i < 22; i++) {
			this.playerSpeed[i].copy(this.getPlayer(i).getSpeed());
			this.drift[i].reset();
			this.secureDistance[i] = this
					.getSecureDist(this.getAllPlayers()[i]);
		}

		// unreliable players don't intercept (we hope)
		for (int i = 0; i < 22; i++) {
			if (this.expectedPlayers[i].isReliable()
					&& !this.expectedPlayers[i].wasOffsideWhenPassed())
				this.considerPlayer[i] = true;
			else
				this.considerPlayer[i] = false;
		}

		switch (this.getWorld().getPlayMode()) {

		// opponents can't get to the ball
		case CORNER_KICK_OWN:
		case FREE_KICK_OWN:
		case GOAL_KICK_OWN:
		case INDIRECT_FREE_KICK_OWN:
		case PENALTY_READY_OWN:
		case KICK_IN_OWN:
		case PENALTY_SETUP_OWN:
			for (int i = 11; i < this.considerPlayer.length; i++) {
				this.considerPlayer[i] = false;
			}
			break;

		// we can't get to the ball
		case CORNER_KICK_OTHER:
		case FREE_KICK_OTHER:
		case GOAL_KICK_OTHER:
		case INDIRECT_FREE_KICK_OTHER:
		case KICK_IN_OTHER:
		case PENALTY_SETUP_OTHER:
		case PENALTY_READY_OTHER:
			for (int i = 0; i < 11; i++) {
				this.considerPlayer[i] = false;
			}
			break;

		default:
			;
		}

		this.inBallPossession = null;
		this.myNextActionWasCalculated = false;
	}

	/**
	 * this method sets all values.
   * it extrapolates the ball cycle by cycle and finds out which players are 
   * able to intercept in this cycle. For that purpose it determines how often 
   * each player has to turn before dashing to the interception point. If a 
   * player is able to intercept in a given number of cycles his corresponding 
   * action is saved for predicting his future movement.
	 */
	public void calculateSituation() {
		this.setUpdated();

		// no update when to far from ball as we don't see nothing anyway
		if (this.getWorld().getDistance(this.getWorld().getBallRef()) > 50
				&& this.fastestPlayers.size() > 0)
			return;

    //force an update of the movements matrix whenever a players status changed
    //(i.e. when his type was communicated)
		Player[] allPlayers = getWorld().getAllPlayers();
		for (Player p : allPlayers) {
			if (p.isStatusChanged()) {
				getMovements().setPlayerMovements(p, true);
				p.setStatusChanged(false);
			}
		}
    
    //no interception in dead play modes
		if (this.getWorld().getPlayMode() == PLAY_MODE.BEFORE_KICK_OFF
				|| this.getWorld().getPlayMode() == PLAY_MODE.KICK_OFF_OTHER) {
			return;
		}

		this.reset();

		// as our movement depends on the current stamina it has to be updated
		// if stamina changes
		getMovements().setPlayerMovements(getWorld().getSelfRef(), false);

		this.b.copy(getWorld().getBallRef());

		double distance;
		double secureDistance_;
		double angleToBall;
		double maxTurnAngle;
		boolean backward = false;
		Vektor finalPosition = new Vektor();

    //extrapolate the ball for each cycle and find out
    //which player can intercept it at the resulting ball position
		for (int cycle = 0; cycle < this.maxCycles; cycle++) {

			this.ballPositions[cycle].copy(this.b.getPosition());

      //set outside cycle when extrapolated ball outside
			if (!getWorld().inField(this.b.getPosition())
					&& this.ballOutsideTime == Integer.MAX_VALUE) {
				this.ballOutsideTime = cycle;
			}

      //someone already intercepted and I am to far away 
      //so i care only about the first interception
			if (this.interceptionTime != Integer.MAX_VALUE
					&& this.interceptionTimes[this.getWorld().getNumber() - 1] == Integer.MAX_VALUE) {
				double myDist = this.getWorld().getSelfRef()
						.getDistance(this.b);
				if (myDist > Math.max(
						Math.max(5 * this.b.getSpeed().getLength(), 7)
								+ this.playerMovements[this.getWorld()
										.getNumber() - 1][cycle][0], 5))
					break;
			}

      //test for each considered player wether he can get the ball at the 
      //resulting position in the given number of cycles
			for (int playerIndex = 0; playerIndex < 22; playerIndex++) {

				if (!this.considerPlayer[playerIndex])
					continue;

				Player p = this.getPlayer(playerIndex);

        //instead of adding a players drift to the player movement we substract 
        //it from the ball which makes no difference
				this.correctedBallPos.reset();
				this.correctedBallPos.addToThis(this.b.getPosition());
				this.correctedBallPos.subFromThis(this.drift[playerIndex]);

				distance = p.getDistance(this.correctedBallPos);
				// players secure radius
				secureDistance_ = this.secureDistance[playerIndex];

        // some players were not seen the last cycle(s) so we give them a 
        // headstart
				int correctedCycle = cycle;
				
        //if the ball was kicked the last cycle by a teammate we assume that the
        //each opponent needs one cycle to react. (he might by chance or 
        //intelligent guessing have forseen our kick, but as we have never met
        //such an opponent ....)
        if (!p.isMe()) {
					if (this.getWorld().getLastHeardMessageType() == MESSAGES.KICKED_BALL) {
						if (!p.isFriend() && correctedCycle > 0)
							correctedCycle--;
					}
				}

        //if a player is tackle dead he can not intercept
				correctedCycle -= this.getPlayer(playerIndex).getTackledeadCountdown();
				if (correctedCycle < 0)
					continue;

				correctedCycle = Math.min(this.maxCycles - 1, correctedCycle);

        //try the best case and if even in this case the player can't intercept
        //continue
				if (distance - secureDistance_ > this.playerMovements[playerIndex][correctedCycle][0]) {
					continue;
				}

        //try all interception modes...
        
				// dashing only
				angleToBall = p.getAngleRel(this.correctedBallPos,
						Player.RELATIVE.TO_BODY);
				backward = Math.abs(angleToBall) > 90
						&& cycle <= this.maxBackwards;

        //ball can be reached by only dashing
				if (Math.abs(Math.sin(Math.toRadians(angleToBall))) * distance < secureDistance_) {
					if (!backward) {
            //the resulting player position after the given number of cycles
						finalPosition.pointAtPolar(
										this.playerMovements[playerIndex][correctedCycle][0],
										p.getBodyDir());
						finalPosition.addToThis(p.getPosition());

            // test if the resulting player will be near enough to the resulting 
            // ball
						if (this.playerMovements[playerIndex][correctedCycle][0] > Math
								.abs(Math.cos(Math.toRadians(angleToBall)))
								* distance
								|| this.correctedBallPos
										.getDistance(finalPosition) < secureDistance_) {
							
              // save the result
              this.finalize(p, cycle, BasicActionFactory
									.dashToPoint(p, this.b.getPosition(),
											correctedCycle));
							if (p.isMe())
								this.rememberMyIP(p, cycle, BasicActionFactory
										.dashToPoint(p, this.b.getPosition(),
												cycle), distance,
										secureDistance_, 0);
							continue;
						}
					} 
          //backward case
          else if (backward) {
						finalPosition.pointAtPolar(
										this.playerMovements[playerIndex][correctedCycle][4],
										p.getBodyDir() + 180);
						finalPosition.addToThis(p.getPosition());

            // test if the resulting player will be near enough to the resulting 
            // ball
						if (this.playerMovements[playerIndex][correctedCycle][4] > Math
								.abs(Math.cos(Math.toRadians(angleToBall)))
								* distance
								|| this.correctedBallPos
										.getDistance(finalPosition) < secureDistance_) {
              
              //save the results
							this.finalize(p, cycle, BasicActionFactory
									.dashToPoint(p, this.b.getPosition(),
											correctedCycle));
							if (p.isMe())
								this.rememberMyIP(p, cycle, BasicActionFactory
										.dashToPoint(p, this.b.getPosition(),
												cycle), distance,
										secureDistance_, 4);
							continue;
						}
					}
				}

				maxTurnAngle = 180.0 / (1 + p.getSpeed().getLength()
						* p.getPConf().INERTIA_MOMENT);

				// turning 1x ->foreward
				if (Math.abs(angleToBall) < maxTurnAngle
						&& distance < this.playerMovements[playerIndex][correctedCycle][1]
								+ secureDistance_) {

          //save the results
				  this.finalize(p, cycle, BasicActionFactory.turnBodyToPoint(
							p, this.correctedBallPos));
					if (p.isMe())
						this.rememberMyIP(p, cycle, BasicActionFactory
								.turnBodyToPoint(p, this.correctedBallPos),
								distance, secureDistance_, 1);
					continue;
				}

				// turning 1x ->backward
				if (cycle <= this.maxBackwards
						&& 180 - Math.abs(angleToBall) < maxTurnAngle
						&& distance < this.playerMovements[playerIndex][correctedCycle][5]
								+ secureDistance_) {
					
          //save the results
          this.finalize(p, cycle, BasicActionFactory.turnBackToPoint(
							p, this.correctedBallPos));
					if (p.isMe())
						this.rememberMyIP(p, cycle, BasicActionFactory
								.turnBackToPoint(p, this.correctedBallPos),
								distance, secureDistance_, 5);
					continue;
				}

				maxTurnAngle += 180.0 / (1 + this.playerSpeed[playerIndex]
						.getLength()
						* p.getPConf().PLAYER_DECAY
						* p.getPConf().INERTIA_MOMENT);

				// turning 2x
				if (distance < this.playerMovements[playerIndex][correctedCycle][2]
						+ secureDistance_) {
					if (Math.abs(angleToBall) < maxTurnAngle) {

            //save the results
						this.finalize(p, cycle, BasicActionFactory
								.turnBodyToPoint(p, this.correctedBallPos));
						if (p.isMe())
							this.rememberMyIP(p, cycle, BasicActionFactory
									.turnBodyToPoint(p, this.correctedBallPos),
									distance, secureDistance_, 2);
						continue;
					}
				}

				// turning 3x
				if (distance < this.playerMovements[playerIndex][correctedCycle][3]
						+ secureDistance_) {

          //save the results
					this.finalize(p, cycle, BasicActionFactory.turnBodyToPoint(
							p, this.correctedBallPos));
					if (p.isMe())
						this.rememberMyIP(p, cycle, BasicActionFactory
								.turnBodyToPoint(p, this.correctedBallPos),
								distance, secureDistance_, 3);
					continue;
				}
			}

			if (cycle < 5)
				this.extrapolateDrift();

      //extrapolate the ball
			if (this.ballOutsideTime == Integer.MAX_VALUE)
				this.b.extrapolate(1, SConf.getInstance().BALL_DECAY);
		}

		//if this plyer is the fastest to the ball try to find the best interception 
    //mode/point
		if (this.iAmFastestToBall()) {

      this.myCatchSecurity = this.getMyCatchSecurity();
			this.myBestInterceptionTime = this
					.getBestInterceptionTime(this.myCatchSecurity);
		}

    //remember who was in possession of the ball
		if (this.getWorld().isViBased()) {
			this.getWorld().getSelfRef().getBallPossessions().insert(
					this.inPossessionOfBall());
			if (this.interceptionTime == 0) {
				this.lastKickingPlayer = this.fastestPlayers.get(0);
			}
		}
	}

	/**
	 * extrapolates player positions when they don't move.
	 * 
	 */
	private void extrapolateDrift() {
		for (int i = 0; i < this.drift.length; i++) {
			this.drift[i].addToThis(this.playerSpeed[i]);
			this.playerSpeed[i].mult(this.getPlayer(i).getPConf().PLAYER_DECAY);
		}
	}

	/**
	 * playerIndex -> player
	 * 
	 * @param index - the player index [0..10] teammates, [11..21] opponents
	 * @return the player corresponding to the index
	 */
	private Player getPlayer(int index) {
		return this.expectedPlayers[index];
	}

	/**
	 * player -> playerIndex
	 * 
	 * @param p - the player
	 * @return the players index
   * 
   * @see #getPlayer(int)
	 */
	private int getPlayerIndex(Player p) {
		if (p.isFriend())
			return p.getNumber() - 1;
		else
			return p.getNumber() - 1 + 11;
	}

	/**
	 * this method saves the values after determing a players interception time.
	 * 
	 * @param p - the player
	 * @param interceptTime - the interception time
	 * @param a - the first action when intercepting
	 */
	private void finalize(Player p, int interceptTime, Action a) {
		if (p.isMe() && this.meAlreadyIntercepted)
			return;

    //the player is the fastest -> set interception point and time
		if (this.fastestPlayers.size() == 0) {
			this.fastestPlayers.add(p);
			this.interceptionTime = interceptTime;
			this.interceptionTimes[this.getPlayerIndex(p)] = interceptTime;
			this.ip.copy(this.ballPositions[interceptTime]);
			this.setAction(p, a);
		} 
    //the player is not the fastest
    //-> remember the player and store the action
    else {
			this.fastestPlayers.add(p);
			this.interceptionTimes[this.getPlayerIndex(p)] = interceptTime;
			this.setAction(p, a);
		}
    //we are only interested in the fastest interception for all players but
    //ourself
		if (!p.isMe()) {
			this.considerPlayer[this.getPlayerIndex(p)] = false;
		}

    //if interception occured reduce the max cycles
		if (!p.isMe() && p.isFriend() && !this.fastestPlayers.get(0).isMe())
			this.maxCycles = Math.min(this.maxCycles,
					(int) (interceptTime * 1.2) + this.maxTimeDiffToTeammate);

		if (!p.isFriend()) {
			if (this.friendAlreadyIntercepted && !this.meAlreadyIntercepted) {
				this.maxCycles = Math.min(this.maxCycles,
						(int) (interceptTime * 1.2) + 1);
			} else {
				this.maxCycles = Math.min(this.maxCycles,
						(int) (this.interceptionTime * 1.2)
								+ this.maxTimeDiffToOpponent);
			}
		}
    
		if (p.isMe()){
			this.meAlreadyIntercepted = true;
      
      //limit horizont
      //(you should turn this off or at least modifiy it when using
      //late interception)
      this.maxCycles = Math.min(this.maxCycles,
          (int) (interceptTime * 1.2) + this.maxTimeDiffToOpponent);   
    }

		if (p.isFriend()) {
			this.friendAlreadyIntercepted = true;
		}
	}

	/**
	 * saves the interception points for this player.
	 * 
	 * @param p - the self 
	 * @param interceptTime - the interception time
	 * @param a - the interception action
	 * @param distance - the overlap when intercepting (the movement a player 
   * could do minus the movement the player has to do for getting the ball.) 
   * <code>distance</code> is always > 0 as otherwise there would be no 
   * interception.
	 * @param secureDistance - the players to the ball which is taken as secure
   * (PLAYER_SIZE + BALL_SIZE + KICKABLE_MARGIN - SECURITY)
	 * @param interceptType - the interception type as described in 
   * {@link Movements}
	 */
	private void rememberMyIP(Player p, int interceptTime, Action a,
			double distance, double secureDistance, int interceptType) {
		int cyclesToPoint = interceptTime;
    
    //find out how much time is needed to get to the interception point
		while (cyclesToPoint >= 0
				&& this.playerMovements[this.getPlayerIndex(p)][cyclesToPoint][interceptType] > distance
						- secureDistance) {
			cyclesToPoint--;
		}
		this.myInterceptionTimes[interceptTime] = cyclesToPoint + 1;
		this.myInterceptActions[interceptTime] = a;
		this.myInterceptionType[interceptTime] = interceptType;
    
    //if we have time, we don't hurry but save some stamina instead
		if (a instanceof DashAction
				&& (this.myInterceptionTimes[interceptTime] > cyclesToPoint + 2 
            || (this.myInterceptionTimes[interceptTime] < 3 
                && this.myInterceptionTimes[interceptTime] > cyclesToPoint + 1))) {
			this.dummyVektor.copy(this.getWorld().getSelfRef().getPosition());
			this.dummyVektor.addToThis(this.getWorld().getSelfRef().getSpeed());

			if (this.dummyVektor.getDistance(this.ballPositions[interceptTime]) > 0.8) {
				p.correctDashPower((DashAction) a, STAMINA_LEVEL.MEDIUM);
			} else {
				p.correctDashPower((DashAction) a, STAMINA_LEVEL.MAX);
			}
		}
	}

	/**
	 * the secure distance based on player.
	 * (PLAYER_SIZE + BALL_SIZE + KICKABLE_MARGIN - SECURITY)
   * 
	 * @param p - the player
	 * @return the distance needed to control the ball
	 */
	private double getSecureDist(Player p) {

		if (p.isGoalie()) {
			if (p.isFriend()) {
				if (getWorld().inOwnPenaltyArea(this.b.getPosition().x,
						this.b.getPosition().y, SConf.getInstance().BALL_SIZE)) {
					return p.getCatchDistance();// - this.securityMates;
				} else
					return p.getKickDistance();// - this.securityMates;
			}

			else
				return p.getCatchDistance() - this.securityOthers;
		} else {
			if (p.isFriend())
				return p.getKickDistance();// - this.securityMates;
			else
				return p.getKickDistance() - this.securityOthers;
		}
	}

	/**
	 * @return true. if self is among the fastest players
	 */
	public boolean iAmFastestToBall() {
		if (!wasUpdated()) {
			this.calculateSituation();
		}
		for (Player p : this.fastestPlayers) {
			if (p.isMe()
					&& this.interceptionTimes[this.getPlayerIndex(p)] == this.interceptionTime)
				return true;
			if (this.interceptionTimes[this.getPlayerIndex(p)] != this.interceptionTime)
				return false;
		}
		return false;
	}

	/**
	 * sets the calculated action to the array.
	 * 
	 * @param p - the player
	 * @param a - the best interception action for the player, null if not 
   * intercepting
	 */
	public void setAction(Player p, Action a) {
		if (!wasUpdated()) {
			this.calculateSituation();
		}
		if (p.isFriend()) {
			this.nextAction[p.getNumber() - 1] = a;
		} else {
			this.nextAction[p.getNumber() - 1 + 11] = a;
		}
	}

	/**
	 * It is not always preferable to intercept the ball fastest possible. 
   * Sometimes it is more secure to go to a later interception point. Sometimes, 
   * the ball already flies in the right direction so we just follow and 
   * intercept later. This method calculates the best interception time. Based 
   * on the balls speed in attack direction.
	 * 
	 * @param catchSecurity - the time this player has till some opponentn 
   * intercepts
	 * @return - the best time to intercept the ball
	 */
	private int getBestInterceptionTime(int catchSecurity) {

		// not safe -> intercept as fast as possible
		if (catchSecurity <= this.maxTimeDiffToOpponent)
			return this.interceptionTime;

		Ball b = getWorld().getBallRef().cloned();
		b.extrapolate(this.interceptionTime + 1, SConf.getInstance().BALL_DECAY);

		int bestInterceptionTime = this.interceptionTime;

		// if the ball is fast, it is easier to miss it so we take the first
		// point
		if (b.getSpeed().getLength() > 1.5 || b.getSpeed().getLength() < 0.4)
			return bestInterceptionTime;

    //this case is handled by the DribbleSituation
		if (this.interceptionTime == 0 
        && this.myInterceptActions[1] != null) {
			this.benefit = 0.0;
			return this.interceptionTime;
		}

		this.benefit = 0.0;
		double tmpValue = 0.0;
		double ballSpeedInGoalDir;
		int interceptionDelay = 0;

    //for each interception possibility calculate the benefit of the resulting
    //situation
		for (int cycle = this.interceptionTime + 1; cycle < this.maxCycles
				&& cycle < this.ballOutsideTime
				&& cycle < this.interceptionTime + catchSecurity; cycle++) {
			
      //no action, no fun
      if (this.myInterceptActions[cycle] == null)
				continue;

			interceptionDelay = cycle - this.myInterceptionTimes[cycle];
			
      //we have much time, so we don't intercept backwards but turn instead
			if (interceptionDelay > 3 && this.myInterceptionType[cycle] == 4) {
				this.myInterceptActions[cycle] = BasicActionFactory
						.turnBodyToPoint(this.getWorld().getSelfRef(),
								this.ballPositions[cycle]);
				this.myInterceptionType[cycle] = 2;
				this.myInterceptionTimes[cycle] = cycle - interceptionDelay + 2;
				// System.out.println(this.getWorld().getCycleAndPlayerString()
				// + " Turning instead of backward " +
				// this.myInterceptActions[cycle] + " " + interceptionDelay);
			} 
      //we have much time, so we don't intercept backwards but turn instead
      else if (interceptionDelay > 2
					&& this.myInterceptionType[cycle] == 5) {
				this.myInterceptActions[cycle] = BasicActionFactory
						.turnBodyToPoint(this.getWorld().getSelfRef(),
								this.ballPositions[cycle]);
				this.myInterceptionType[cycle] = 2;
				this.myInterceptionTimes[cycle] = cycle - interceptionDelay + 1;
			}
			
      //get the benefit for the resulting situation
			tmpValue = SituationEvaluator.getValue(
					SituationEvaluator.TYPE.BALL_INTERCEPTION, this.getWorld()
							.getBallRef().getPosition(), b.getPosition(), Math
							.min(5, this.interceptionTime
									+ (int) (catchSecurity * (b.getSpeed()
											.getLength() + 0.5)) - cycle),
					cycle, this.getWorld().getSelfRef(), this.getWorld());

			if (true || cycle > this.interceptionTime) {
        
        //punish backward
				if (this.myInterceptionType[cycle] > 3) {
					tmpValue *= 0.8 * Math.max(0.1,
							1 - this.myInterceptionTimes[cycle] / 30.0);
				}
			}

      //if a better soluation was found -> make some more considerations
			if (tmpValue > benefit) {
				if (cycle > this.interceptionTime) {
					this.dummyVektor.copy(SConf.getInstance().GOAL_POS_OTHER);
					this.dummyVektor.subFromThis(b.getPosition());
					this.dummyVektor.setLength(1.0);
					ballSpeedInGoalDir = this.dummyVektor.scalar(b.getSpeed());
					
          //no sense taking a late intercept as the ball goes in the wrong
          //direction -> punish it
          if (ballSpeedInGoalDir <= 0.3) {
						tmpValue *= 0.5;
					} 
          //no sense taking a late intercept even so the ball goes in the right
          //direction as the player is faster than the ball
          else if (ballSpeedInGoalDir
							- this.getWorld().getSelfRef().getSpeed()
									.getLength() < 0.6) {
						tmpValue *= 0.75;
					}
				}
			}

      //if a better soluation was found -> store it
			if (tmpValue > benefit) {
				bestInterceptionTime = cycle;
				benefit = tmpValue;
			} 
      else {
				break;
			}
      
			b.extrapolate(this.interceptionTime,SConf.getInstance().BALL_DECAY);

		}

		return bestInterceptionTime;
	}

	/**
	 * gets the calculated action for a given player from the array.
	 * 
	 * @param p - the player
	 * @return last calculated intercept ball action, null if player isn't among
	 *         the fastest players
	 */
	public Action getNextAction(Player p) {
		if (!wasUpdated()) {
			this.calculateSituation();
		}

		if (p.isMe() 
        && this.iAmFastestToBall()
				&& !this.ballOutBeforeIntercept()
				&& !this.myNextActionWasCalculated) {

			this.myNextActionWasCalculated = true;

      //if near interception, do not only intercept but intercept the best
      //way possible
			if (this.myBestInterceptionTime < 3
					|| this.getWorld().getSelfRef().getDistance(
							this.ballPositions[this.myBestInterceptionTime]) < 0.9) {
				this.ip.copy(this.ballPositions[this.myBestInterceptionTime]);
				this.nextAction[getWorld().getNumber() - 1] = this
						.getNextCloseInterceptAction(this.myBestInterceptionTime);
				return this.nextAction[getWorld().getNumber() - 1];
			} 
      //same as above but kept simpler
      else if (this.myBestInterceptionTime < 7) {
				Action correctedInterceptAction = this
						.optimizeInterception(this.myBestInterceptionTime);

				if (correctedInterceptAction != null) {
					this.ip.copy(this.ballPositions[this.myBestInterceptionTime]);
					this.nextAction[getWorld().getNumber() - 1] = correctedInterceptAction;
					return this.nextAction[getWorld().getNumber() - 1];
				}
			}

      //standard interception for far away balls
			Action myAction = this.myInterceptActions[this.myBestInterceptionTime];
			if (myAction == null) {
				System.out.println(this.getWorld().getCycleAndPlayerString()
						+ " InterceptBallSituation - action = null");
				return null;
			}

      //dashing is always prefered as it keeps us going
			if (myAction.getType() == Action.TYPE.DASH) {
				DashAction dash = (DashAction) myAction;
				if (dash.getPower() > 0
						|| this.myBestInterceptionTime - this.interceptionTime < 3) {
					this.ip.copy(this.ballPositions[this.myBestInterceptionTime]);
					this.nextAction[getWorld().getNumber() - 1] = myAction;
					return myAction;
				}
			}

      //if we would have to turn instead we try to ind if one of the later 
      //points might be intercepted in a save way by dashing
      //without this the player would turn too often
      
      //try if dashing gets the ball the previous cycle
			for (int cycle = this.myBestInterceptionTime; cycle > this.myBestInterceptionTime - 2
					&& cycle > 0; cycle--) {
				myAction = this.myInterceptActions[cycle];
				if (myAction == null)
					continue;
				if (myAction.getType() == Action.TYPE.DASH) {
					this.myBestInterceptionTime = cycle;
					this.ip.copy(this.ballPositions[cycle]);
					this.nextAction[getWorld().getNumber() - 1] = myAction;
					return myAction;
				}
			}

      //try if dashing gets the ball in one of the next cycles
			for (int cycle = this.myBestInterceptionTime; cycle < this.myBestInterceptionTime
					+ this.myCatchSecurity - 2
					&& cycle < this.maxCycles; cycle++) {
				myAction = this.myInterceptActions[cycle];
				if (myAction == null)
					continue;
				if (myAction.getType() == Action.TYPE.DASH) {
					this.myBestInterceptionTime = cycle;
					this.ip.copy(this.ballPositions[cycle]);
					this.nextAction[getWorld().getNumber() - 1] = myAction;
					return myAction;
				}
			}

      //try an earlier point if more save
			for (int cycle = this.myBestInterceptionTime; cycle >= this.interceptionTime; cycle--) {
				if (this.myInterceptionTimes[cycle] < cycle) {
					this.myBestInterceptionTime = cycle;
					this.ip.copy(this.ballPositions[cycle]);
					this.nextAction[getWorld().getNumber() - 1] = this.myInterceptActions[cycle];
					return this.myInterceptActions[cycle];
				}
			}

      //try a later point if more save
			for (int cycle = this.myBestInterceptionTime; cycle < this.myBestInterceptionTime
					+ this.myCatchSecurity - 2
					&& cycle < this.maxCycles; cycle++) {
				if (this.myInterceptionTimes[cycle] < cycle) {
					this.myBestInterceptionTime = cycle;
					this.ip.copy(this.ballPositions[cycle]);
					this.nextAction[getWorld().getNumber() - 1] = this.myInterceptActions[cycle];
					return this.myInterceptActions[cycle];
				}
			}

			return this.nextAction[p.getNumber() - 1];
		}

    //return the stored values
		if (p.isFriend())
			return this.nextAction[p.getNumber() - 1];
		else
			return this.nextAction[p.getNumber() - 1 + 11];
	}

	/**
	 * close interception is calculated in more detail.
	 * 
	 * @return intercept Action for near balls
	 */
	private Action getNextCloseInterceptAction(int delay) {
		
    // set next ball
		this.b.copy(getWorld().getBallRef());
		this.b.extrapolate(delay, SConf.getInstance().BALL_DECAY);

		// next player when dashing to ball
		this.p.copy(getWorld().getSelfRef());
		this.p.predictThisAfterAction(this.myInterceptActions[interceptionTime]);

		double distanceWhenDashing = this.b.getDistance(this.p);

		boolean collisionPoss = distanceWhenDashing < this.p.getPConf().PLAYER_SIZE
				&& !this.getWorld().getSelfRef().isGoalie();
		boolean ballFast = this.b.getSpeed().getLength() > 1.8;
		boolean nearToNoDash = this.myInterceptActions[interceptionTime] != null
				&& this.myInterceptActions[interceptionTime] instanceof DashAction
				&& Math.abs(((DashAction) this.myInterceptActions[interceptionTime])
								.getPower()) < 5;

		// player without dashing
		this.p2.copy(getWorld().getSelfRef());
		this.p2.extrapolate(1, this.p.getPConf().PLAYER_DECAY);

		double distanceWithoutDashing = this.b.getDistance(this.p2);

		this.dummyVektor.copy(this.getPointToTurnTo());

		// needed if player isn't turned in direction of opp. goal
		// turning is very useful as we might pass to someone we don't see
		// without it
		boolean turningNeeded = Math.abs(this.p2.getAngleRel(this.dummyVektor,
				Player.RELATIVE.TO_BODY)) > 20
				&& !this.getWorld().getSelfRef().isGoalie();

		// ball fast but no dashing needed for collision so we turn
		if (distanceWithoutDashing < p.getPConf().PLAYER_SIZE * 0.8
				&& turningNeeded && ballFast) {
			if (DEBUG)
				System.out.println(this.getWorld().getCycleAndPlayerString()
						+ " CloseIntercept1 - turning for collision");
			return BasicActionFactory.turnBodyToPoint(this.getWorld()
					.getSelfRef(), this.dummyVektor);
		}

		// dashing needed for collision or no turn needed -> dash to stop ball
		if (collisionPoss && (ballFast || !turningNeeded)) {
			if (DEBUG)
				System.out.println(this.getWorld().getCycleAndPlayerString()
						+ " CloseIntercept1 - dashing for collision");
			return this.nextAction[getWorld().getNumber() - 1];
		}

		// try turning to a better direction as the ball comes to us
		if ((distanceWithoutDashing < this.p2.getKickDistance() * 0.9 || nearToNoDash)
				&& turningNeeded) {
			this.p2.copy(getWorld().getSelfRef());
			this.p2.predictThisAfterAction(BasicActionFactory.turnBodyToPoint(
					getWorld().getSelfRef(), this.dummyVektor));
			// we have to turn at least 2x -> try to collide and slow down (and
			// turn 1x afterwards)
			if (Math.abs(this.p2.getAngleRel(this.dummyVektor,
					Player.RELATIVE.TO_BODY)) > 20.0
					&& collisionPoss) {
				return this.myInterceptActions[interceptionTime];
			}
			return BasicActionFactory.turnBodyToPoint(getWorld().getSelfRef(),
					this.dummyVektor);
		}

		// we have to dash and the ball is slow enough to be kicked in next
		// cycle -> get the ball to a good kick position by trying to get the ball
    // to a certain distance to the next player
		if (((distanceWhenDashing < this.p2.getPConf().KICKABLE_MARGIN - 0.2) || (this
				.getWorld().getSelfRef().isGoalie() && distanceWhenDashing < 1.8)
				&& !ballFast)) {
			double bestDist = SConf.getInstance().BALL_SIZE
					+ this.p.getPConf().PLAYER_SIZE
					+ this.p.getPConf().KICKABLE_MARGIN / 6;

			Line playerAccelLine = new Line(this.p2.getPosition(), this.p2
					.getPosition().add(new Vektor(1.0, this.p2.getBodyDir())));
			Circle bestCircle = new Circle(this.b.getPosition(), bestDist);

			LinkedList<Vektor> bestIPoints = playerAccelLine
					.getIntersectionPoints(bestCircle);
			Vektor bestIPoint = null;

			// Ipoint exists
			if (bestIPoints.size() > 0) {
				// tangent
				if (bestIPoints.size() == 1) {
					bestIPoint = bestIPoints.get(0);
				}
				// secant
				else {
					double angle0 = this.p2.getAngleAbs(bestIPoints.get(0),
							Player.RELATIVE.TO_BODY);
					double angle1 = this.p2.getAngleAbs(bestIPoints.get(1),
							Player.RELATIVE.TO_BODY);
					if (abs(angle0) < abs(angle1))
						bestIPoint = bestIPoints.get(0);
					else
						bestIPoint = bestIPoints.get(1);
				}

				return BasicActionFactory.dashToPoint(getWorld().getSelfRef(),
						bestIPoint);
			}
		}

		return this.myInterceptActions[interceptionTime];
	}

	/**
	 * optimizes a given interception. first we only checked if dashing could
	 * get the ball. Now we check if instead of only dashing a turn as first
	 * action would bring us closer. This is use for near but not close 
   * intercepts.
	 * 
	 * @param interceptionTime -
	 *            our interception time
	 * @return - the optimized intercept action, null if no optimization was found
	 */
	private Action optimizeInterception(int interceptionTime) {
    
    //only near intercepts, only dashing as turning will already get as close
    //as possible to the ball, only save
		if (interceptionTime > 6
				|| this.myInterceptActions[interceptionTime] instanceof TurnAction
				|| this.myInterceptionTimes[interceptionTime] == interceptionTime) {
			return null;
		}

		this.p.copy(this.getWorld().getSelfRef());
		this.p.extrapolate(interceptionTime, p.getPConf().PLAYER_DECAY);

		double angleToBall = this.p.getAngleRel(
				this.ballPositions[interceptionTime], RELATIVE.TO_BODY);
		double distanceToBall = this.p
				.getDistance(this.ballPositions[interceptionTime]);

		double distanceWhenDashing = Math.abs(Math.sin(Math
				.toRadians(angleToBall)))	* distanceToBall;

		double interceptDistance = (p.isGoalie() && this.getWorld()
				.inOwnPenaltyArea(this.ballPositions[interceptionTime].x,
						this.ballPositions[interceptionTime].y, 0)) ? p
				.getCatchDistance() : p.getKickDistance();

    //dashing best, as it is already bringing as very close to the ball
		if (distanceWhenDashing < 0.7 * interceptDistance) {
			return null;
		}

		double angleToNextBall = this.p.getAngleRel(
				this.ballPositions[interceptionTime + 1], RELATIVE.TO_BODY);
		double distanceToNextBall = this.p
				.getDistance(this.ballPositions[interceptionTime + 1]);

		double distanceWhenDashingNext = Math.abs(Math.sin(Math
				.toRadians(angleToNextBall)))
				* distanceToNextBall;

    //dashing best, as as even so it doesn't bring us very close to the ball
    //at the given intercption point, the ball of the next cycle will be very 
    //close so we are unlikely to miss it
		if (distanceWhenDashingNext < 0.7 * interceptDistance) {
			return null;
		}

		this.p.copy(this.getWorld().getSelfRef());

    //try turning first otherwise
		TurnAction turn = BasicActionFactory.turnBodyToPoint(this.p,
				this.ballPositions[interceptionTime], interceptionTime);
		this.p.predictThisAfterAction(turn);

		double resDistance = this.getMovements().getLeftDistance(
				this.getWorld().getNumber() - 1, interceptionTime - 1,
				this.ballPositions[interceptionTime], this.p.getPosition(),
				this.p.getBodyDir(), 0, 0, interceptDistance, 1.0)
				+ interceptDistance;

    //turning gets us nearer to the ball -> turn first
		if (resDistance < distanceWhenDashing - 0.2) {
			return turn;
		} else {
      return null;
		}
	}

	/**
	 * gets the point to turn to when intercepting in 1 leaves time for turning.
	 * (most of the times we turn in y-dir). The point depends on the players role
   * and its position
	 * 
	 * @return point to turn to
	 */
	private Vektor getPointToTurnTo() {
		Vektor v = Vektor.getDummyInstance();

		switch (this.getWorld().getSelfRef().getRoleByRoleNumber()) {
		case LEFT_ATTACKER:
		case LEFT_MIDFIELD:
		case LEFT_BACK_FOUR:
			v.copy(SConf.getInstance().GOAL_POST_LEFT_OTHER);

			if (this.ip.y < 40) {
				double x = ip.x;
				if (x < -25)
					x = -25;
				else if (x > v.x)
					x = v.x;

				v.x = x;
			}
			return v;

		case RIGHT_ATTACKER:
		case RIGHT_MIDFIELD:
		case RIGHT_BACK_FOUR:
			v.copy(SConf.getInstance().GOAL_POST_RIGHT_OTHER);

			if (this.ip.y < 40) {
				double x = ip.x;
				if (x > 25)
					x = 25;
				else if (x < v.x)
					x = v.x;

				v.x = x;
			}
			return v;

		default:
			return SConf.getInstance().GOAL_POS_OTHER;
		}
	}

	/**
	 * the time we have after catching the ball before an opponent will reach
	 * that position. <br>
   * TODO: could be more accurate as by now only the opponents
	 * ball interception time is taken into account. instead his time to my ip
	 * should be used. (rw)
	 * 
	 * @return timediff off my and next opponent ball interception
	 */
	private int getMyCatchSecurity() {
		if (this.fastestPlayers.size() == 0
				|| !this.fastestPlayers.get(0).isMe())
			return 0;
		else {
			int myTime = this.interceptionTime;
			int nextTime = -1;
			for (Player p : this.fastestPlayers) {
				if (!p.isMe() && !p.isFriend()) {
					nextTime = this.interceptionTimes[this.getPlayerIndex(p)];
					break;
				}
			}
			if (nextTime < 0)
				nextTime = myTime + this.maxTimeDiffToTeammate;

			nextTime = Math.min(nextTime, this.ballOutsideTime);

			return nextTime - myTime;
		}
	}

	/** **************************** Prophet methods ********* */

	/**
	 * @return Returns the fastestPlayer.
	 */
	public LinkedList<Player> getFastestPlayer() {
		if (!wasUpdated()) {
			this.calculateSituation();
		}
		return this.fastestPlayers;
	}

	/**
	 * @return Returns the interceptionTime.
	 */
	public int getInterceptionTime() {
		if (!wasUpdated()) {
			this.calculateSituation();
		}
		return this.interceptionTime;
	}

	/**
	 * @return Returns the interceptionTime.
	 */
	public boolean ballOutBeforeIntercept() {
		if (!wasUpdated()) {
			this.calculateSituation();
		}
		return this.ballOutsideTime <= this.getInterceptionTime();
	}

	/**
	 * @return Returns the ip.
	 */
	public Vektor getIp() {
		if (!wasUpdated()) {
			this.calculateSituation();
		}
		return this.ip;
	}

	/**
	 * @return Returns the myInterceptionTimes.
	 */
	public int[] getMyInterceptionTimes() {
		if (!wasUpdated()) {
			this.calculateSituation();
		}
		return this.myInterceptionTimes;
	}

	/**
	 * tells who is in possesion of the ball (team of fastest to ball)
	 * 
	 * @return
	 */
	public ControllerNumbers.TEAM inPossessionOfBall() {
		if (!wasUpdated()) {
			this.calculateSituation();
		}

		if (this.inBallPossession == null) {
      
      //don't always switch but use a tolerance as noise might lead to wrong
      //data
			int tolerance = ((int) this.getWorld().getDistance(
					this.getWorld().getBallRef())) % 12;

			boolean our = false;
			boolean their = false;
			for (Player p : this.fastestPlayers) {
				if (this.interceptionTimes[this.getPlayerIndex(p)] > this.interceptionTime
						+ tolerance) {
					break;
				}

				if (p.isFriend())
					our = true;
				else
					their = true;
			}

			if (our && their)
				this.inBallPossession = ControllerNumbers.TEAM.UNCERTAIN;
			else if (our)
				this.inBallPossession = ControllerNumbers.TEAM.WE;
			else
				this.inBallPossession = ControllerNumbers.TEAM.OPPONENT;
		}

		return this.inBallPossession;
	}

	/**
	 * the ball interception time of a given player (used i.e. for gui)
	 * 
	 * @param number - the players number
	 * @param team - one of our team?
	 * @return the players interception time, {@link Integer#MAX_VALUE} if
   * not intercepting
	 */
	public int getInterceptionTime(int number, boolean team) {
		if (!wasUpdated()) {
			this.calculateSituation();
		}
		if (team)
			return this.interceptionTimes[number - 1];
		else
			return this.interceptionTimes[number + 11 - 1];
	}

	/**
	 * This method checks, if the given player is fastest to the ball
	 * 
	 * @param p -
	 *            the considered player
	 * @return true, if he is fastest to ball when intercepting
	 */
	public boolean isFastestToBall(Player p) {
		if (!wasUpdated()) {
			this.calculateSituation();
		}
    
		LinkedList<Player> player = this.getFastestPlayer();
		for (Player fs : player) {
			if (fs.isFriend() == p.isFriend()
					&& p.getNumber() == fs.getNumber())
				return true;
		}
		return false;
	}

	/**
	 * @return all players with the ball in their kickable margin
	 */
	public LinkedList<Player> getPlayersThatCanKick() {
		if (!wasUpdated()) {
			this.calculateSituation();
		}

		// no player can kick or already calculated -> return list
		if (this.interceptionTime != 0 || this.playersThatCanKick.size() > 0) {
			return this.playersThatCanKick;
		}

		for (Player p : this.fastestPlayers) {
			if (this.getInterceptionTime(p.getNumber(), p.isFriend()) == 0) {
				this.playersThatCanKick.add(p);
			}
			// players are sorted...
			else
				break;
		}

		return this.playersThatCanKick;
	}

	/**
	 * @return Returns the lastKickingPlayer.
	 */
	public Player getLastKickingPlayer() {
		Player lastKicker = this.lastKickingPlayer;

		if (!wasUpdated()) {
			this.calculateSituation();
		}

		if (lastKicker != this.lastKickingPlayer
				&& this.getWorld().getNumber() == 10) {
			System.out.println(this.getWorld().getCycleAndPlayerString()
					+ " KICKING PLAYER " + this.lastKickingPlayer);
		}

		return this.lastKickingPlayer;
	}

	/**
	 * @return Returns the ballWillGoIntoOwnGoal.
	 */
	public boolean ballWillGoIntoOwnGoal() {
		if (!wasUpdated()) {
			this.calculateSituation();
		}

		this.ballWillGoIntoOwnGoal = this.ip.y - 0.5 < SConf.getInstance().GOAL_POS_OWN.y
				&& Math.abs(this.ip.x) < SConf.getInstance().GOAL_WIDTH / 2 + 1;

		return this.ballWillGoIntoOwnGoal;
	}

	/**
	 * @return Returns the ballWillGoIntoOwnGoal.
	 */
	public boolean ballWillGoIntoOpponentsGoal() {
		if (!wasUpdated()) {
			this.calculateSituation();
		}

		this.ballWillGoIntoOpponentsGoal = this.ip.y > SConf.getInstance().GOAL_POS_OTHER.y - 0.1
				&& Math.abs(this.ip.x) < SConf.getInstance().GOAL_WIDTH;

		return this.ballWillGoIntoOpponentsGoal;
	}
}
